package cn.ciis.server;

import cn.ciis.basic.base.BaseCache;
import cn.ciis.basic.event.OpenDebugEvent;
import cn.ciis.basic.event.RateLimiterEvent;
import cn.ciis.basic.kit.ByteKit;
import cn.ciis.basic.kit.CiisKit;
import cn.ciis.basic.kit.EnumKit;
import cn.ciis.basic.kit.J2CacheKit;
import cn.ciis.basic.model.Enterprise;
import cn.ciis.basic.model.Equipment;
import cn.ciis.basic.model.mongo.DeviceConnectInfo;
import cn.ciis.basic.plugin.CiisSessionContext;
import cn.ciis.ciis.enterprise.EnterpriseAdminService;
import cn.ciis.ciis.equipment.EquipmentAdminService;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cybermkd.mongo.kit.MongoQuery;
import com.jfinal.aop.Aop;
import com.jfinal.kit.LogKit;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import net.dreamlu.event.EventKit;
import net.oschina.j2cache.CacheObject;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.tio.core.ChannelContext;
import org.tio.core.Tio;
import org.tio.core.intf.Packet;
import org.tio.core.stat.ChannelStat;
import org.tio.monitor.RateLimiterWrap;
import org.tio.server.intf.ServerAioListener;

import java.util.Date;
import java.util.Optional;

/**
 * @author 冉志林
 */
public class CiisServerListener implements ServerAioListener {
    
    @Override
    public boolean onHeartbeatTimeout(ChannelContext channelContext, Long aLong, int i) {
        return false;
    }

    private EnterpriseAdminService eas= Aop.get(EnterpriseAdminService.class);//企业
    private EquipmentAdminService eias= Aop.get(EquipmentAdminService.class);//设备

    /**
     * 建链后触发本方法，注：建链不一定成功，需要关注参数isConnected
     *
     * @param channelContext
     * @param isConnected
     * @param isReconnect
     * @throws Exception
     */
    @Override
    public void onAfterConnected(
            ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception {
        if (isConnected) {

            int permitsPerSecond = PropKit.getInt("request.permitsPerSecond", 4000000);
            int warnClearInterval = 1000 * PropKit.getInt("request.warnClearInterval");
            int maxWarnCount = PropKit.getInt("request.maxWarnCount");
            int maxAllWarnCount = PropKit.getInt("request.maxAllWarnCount");
            RateLimiterWrap rateLimiterWrap = new RateLimiterWrap(permitsPerSecond, warnClearInterval, maxWarnCount, maxAllWarnCount);
            CiisSessionContext ciisSessionContext = new CiisSessionContext();

            ciisSessionContext.setRequestRateLimiter(rateLimiterWrap);

            String ip = channelContext.getClientNode().getIp();

            DbSearcher dbSearcher = null;
            DataBlock dataBlock = null;
            try {

                String dbpath = FileUtil.getAbsolutePath("config/ip2region/ip2region.db");
                if(FileUtil.isFile(dbpath)){
                    
                    DbConfig config = new DbConfig();
                    dbSearcher = new DbSearcher(config, dbpath);
                    dataBlock = dbSearcher.memorySearch(ip);
                }else{
                    LogKit.error("请检查一下文件是否存在:{}", dbpath);
                }
                dataBlock = dbSearcher.memorySearch(ip);

            } catch (Exception e) {
                LogKit.error(e.toString(), e);
            } finally {
                if (dataBlock == null) {
                    dataBlock = new DataBlock(0, "未知", 0);
                }
                LogKit.info("IP:{},地址信息:{}",ip,dataBlock.getRegion());
                ciisSessionContext.setDataBlock(dataBlock);
                channelContext.set("ciisSessionContext",ciisSessionContext);
            }
            LogKit.info("建链后触发本方法" + System.currentTimeMillis());
        }
    }

    /**
     * 解码成功后触发本方法
     *
     * @param channelContext
     * @param packet
     * @param i
     * @throws Exception
     */
    @Override
    public void onAfterDecoded(ChannelContext channelContext, Packet packet, int i) {
        LogKit.info("解码成功后触发本方法" + System.currentTimeMillis());

        CiisSessionContext ciisSessionContext = (CiisSessionContext) channelContext.get("ciisSessionContext");

        CiisPacket ciisPacket = (CiisPacket) packet;
        if(StrKit.isBlank(channelContext.getBsId())){//没有绑定过业务

            if(!ciisSessionContext.getEnterprise().isPresent()){

                String enterpriseCode = ByteKit.bytesToHexString(ciisPacket.getEnterprise());//企业号
                CacheObject cacheEnterprise = J2CacheKit.get(BaseCache.ENTERPRISE_TIMED_CACHE, enterpriseCode);
                boolean[] flag = new boolean[]{true};
                if(null == cacheEnterprise || ObjectUtil.isNull(cacheEnterprise.getValue())){//缓存缓存中没有找到设备信息
                    J2CacheKit.set(BaseCache.ENTERPRISE_TIMED_CACHE,enterpriseCode,eas.findByCode(enterpriseCode));
                    flag[0] = false;
                }else{
                    Enterprise enterprise = (Enterprise) cacheEnterprise.getValue();//Convert.convert(Enterprise.class,cacheEnterprise);
                    Optional<String> code = Optional.ofNullable(enterprise.getCode());
                    if(code.filter(val-> StrKit.notBlank(val)).isPresent()){
                        ciisSessionContext.setEnterprise(enterprise);
                    }else{
                        flag[0] = false;
                    }
                }
                if(!flag[0]){
                    //考虑拉黑设备
                    LogKit.error("企业code不存在{}",enterpriseCode);
                }
            }
            if(!ciisSessionContext.getEquipment().isPresent()){
                String equipmentCode = ByteKit.bytesToHexString(ciisPacket.getDevice());//设备code
                CacheObject cacheObject = J2CacheKit.get(BaseCache.EQUIPMENT_TIMED_CACHE, equipmentCode, s -> {
                    return eias.findByCode(equipmentCode);
                });

                Optional<Equipment> equipment = Optional.ofNullable((Equipment) cacheObject.getValue());
                boolean[] flag = new boolean[]{true};
                if(!equipment.isPresent()){//缓存没有找到设备信息
                    flag[0] = false;
                }else{
                    Optional<Integer> id = Optional.ofNullable(equipment.get().getId());
                    if(id.filter(val -> val > 0).isPresent()){
                        ciisSessionContext.setEquipment(equipment.get());
                    }else{
                        flag[0] = false;
                    }
                }

                if(!flag[0]){
                    LogKit.error("设备code不存在{}",equipmentCode);
                }
            }

            LogKit.info("绑定业务ID，修改设备在线状态");
            ciisSessionContext.getEquipment().ifPresent(equipment -> {
                equipment.setState(1);//在线
                equipment.setIp(channelContext.getClientNode().getIp());//客户端IP
                equipment.setPort(channelContext.getClientNode().getPort());//客户端端口
                equipment.setConnectionAt(new Date());//连接时间
                equipment.setDisconnectionAt(null);//断开连接时间
                equipment.update();//修改设备在数据库中的状态

                String bsId = CiisKit.getTioBsId(ciisSessionContext.getEnterprise().get(), ciisSessionContext.getEquipment().get());
                Tio.bindGroup(channelContext,ciisSessionContext.getEnterprise().get().getCode());
                Tio.bindBsId(channelContext,bsId);
            });
        }

        EventKit.post(new OpenDebugEvent(ciisPacket,channelContext.getBsId(),true));
    }

    /**
     * 接收到TCP层传过来的数据后
     *
     * @param channelContext
     * @param receivedBytes  本次接收了多少字节
     * @throws Exception
     */
    @Override
    public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) {

        LogKit.info("接收到TCP层传过来的数据后" + System.currentTimeMillis());
        if(PropKit.getBoolean("israte",true)){//流控开启

            RateLimiterEvent rateLimiterEvent = new RateLimiterEvent(channelContext);
            EventKit.post(rateLimiterEvent);

        }

    }

    /**
     * 消息包发送之后触发本方法
     *
     * @param channelContext
     * @param packet
     * @param isSentSuccess  true:发送成功，false:发送失败
     * @throws Exception
     */
    @Override
    public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) {
        if (isSentSuccess) {
            LogKit.info("息包发送之后触发本方法" + System.currentTimeMillis());
            CiisPacket ciisPacket = (CiisPacket) packet;
            EventKit.post(new OpenDebugEvent(ciisPacket,channelContext.getBsId(),false));
        }
    }

    /**
     * 处理一个消息包后
     *
     * @param channelContext
     * @param packet
     * @param cost           本次处理消息耗时，单位：毫秒
     * @throws Exception
     */
    @Override
    public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) {
        LogKit.info("处理一个消息包后" + System.currentTimeMillis());
    }

    /**
     * 连接关闭前触发本方法
     *
     * @param channelContext
     * @param throwable
     * @param remark
     * @param isRemove
     * @throws Exception
     */
    @Override
    public void onBeforeClose(
            ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) {
        if (isRemove) {

            CiisSessionContext ciisSessionContext = (CiisSessionContext) channelContext.get("ciisSessionContext");

            //会话中包含了设备信息
            if(ciisSessionContext.getEquipment().isPresent()&&ciisSessionContext.getEnterprise().isPresent()){
                ciisSessionContext.getEquipment().get().setDisconnectionAt(new Date());
                ciisSessionContext.getEquipment().get().setState(0);

                ciisSessionContext.getEquipment().get().update();//更新数据库中设备状态
                MongoQuery query=new MongoQuery();
                ChannelStat stat = channelContext.stat;

                DeviceConnectInfo deviceConnectInfo = new DeviceConnectInfo(ciisSessionContext.getEquipment().get(),ciisSessionContext.getEnterprise().get().getCode(),stat);
                deviceConnectInfo.setAddress(ciisSessionContext.getDataBlock().getRegion());
                LogKit.info("保存到MongoDB的数据是:{}",deviceConnectInfo.toJSONString());
                boolean save = query.use(EnumKit.MongoCollect.devices_connect_info.toString()).set(deviceConnectInfo.toJSONObject()).save();
                if(!save){
                    J2CacheKit.evict(BaseCache.EQUIPMENT_TIMED_CACHE, ciisSessionContext.getEquipment().get().getCode());
                    LogKit.error("MongoDB保存设备在线信息失败。");
                }
            }
            LogKit.info("连接关闭前触发本方法,客户端{}断开了连接{}", channelContext.getClientNode(), +System.currentTimeMillis());
        }
    }
}
